using System;
using System.Collections;
using System.Text;
using Spring.Util;

namespace Sedna.Core.Common.Objects.Navigation
{
	public class ScopePathParser: IScopePathParser
	{
		private IDictionary aliases = new Hashtable();
		private IList prefixes = new ArrayList();
		private IList postfixes = new ArrayList();
	    
        #region Static stuff
        private static IScopePathParser instance = new ScopePathParser();
	    
	    /// <summary>
	    /// Do not use this property directly in your actions. Inject runtime.ScopePathParser instead.
	    /// </summary>
	    public static IScopePathParser Instance
	    {
            get { return instance; }
	    }
	    #endregion 

        public ScopePathParser()
		{
			aliases.Add("string", typeof(string));

			prefixes.Add("Sedna.Domain.");

			postfixes.Add(", Sedna.Domain");
		    
			postfixes.Add(", Sedna");
        }


        #region Public Methods

        public IScopePath Parse(Scope scope, string keyAndPath)
		{
			return new ScopePath(scope.ToString()+keyAndPath, ParsePath(scope, null, keyAndPath));
		}
	
		public IScopePath Parse(Scope scope, Type objectType, string key, string path)
		{
			string fullPath=String.Format("{0}{1}['{2}']{3}", scope.ToString(), objectType!=null?"{"+objectType.ToString()+"}":String.Empty, key, path.Length>0&&!IsNodeSeparator(path[0])?"."+path:path);
			return new ScopePath(fullPath, ParsePath(scope, CreateScopedNode(scope, key, objectType), path));		
		}

		public IScopePath Parse(object rootObject, string path)
		{
			NavigationNode rootNode=null;
			if (rootObject!=null)
			{
				rootNode=new RootNode(rootObject);
			}
			return new ScopePath(path, ParsePath(Scope.ExecutionContext, rootNode, path));
		} 

		/// <summary>
		/// Parses expression and creates appropriate NavigationNode instances.
		/// </summary>
		/// <param name="path">Expression to parse.</param>
		/// <returns>Parsed path.</returns>
		public IScopePath Parse(string path)
		{
			return new ScopePath(path, ParsePath(Scope.ExecutionContext, null, path));
        }

	    public virtual object GetScopedValue(Scope scope, string key)
	    {
	        return ScopedNode.GetScopedValue(key, scope);
	    }

	    public virtual void SetScopedValue(Scope scope, string key, object value)
	    {
	        ScopedNode.SetScopedValue(key, value, scope);
	    }

	    #endregion

        #region Private Methods

        private  NavigationNode ParsePath(Scope rootScope, NavigationNode rootNode, string path)
		{
			if (StringUtils.IsNullOrEmpty(path))
			{
				return rootNode;
			}


			StringBuilder sb = new StringBuilder();
			NavigationNode lastNode = rootNode;
			Scope scope = rootScope;
			NavigationNode node = null;
			Type nodeType = null;
			int pos = 0;
			char c;





			while (pos < path.Length)
			{
				c = path[pos++];
				while (!IsNodeSeparator(c) && pos < path.Length)
				{
					sb.Append(c);
					c = path[pos++];
				}
				if (pos == path.Length || c == '.')  // end of expression 
				{
					if (!IsNodeSeparator(c))
					{
						sb.Append(c);
					}
					if (sb.Length > 0)
					{
						string element = sb.ToString().Trim();
						if (element[0] == '\'')    
						{
							node = new StringLiteralNode(lastNode, element);
						}
						else if (element[0] == '#')
						{
							node = new DateLiteralNode(lastNode, element);
						}
						else if (element[0] >= '0' && element[0] <= '9')
						{
							node = new NumericLiteralNode(lastNode, element);
						}
						else
						{
							node = new PropertyOrFieldNode(lastNode, element, nodeType);
						}
					}
				}
				else if (c == '(')      // method
				{
					while (c != ')' && pos < path.Length)
					{
						sb.Append(c);
						c = path[pos++];
					}
					sb.Append(c);
					node = new MethodNode(lastNode, sb.ToString());
				}
				else if (c == '[')      // indexer
				{
					if (lastNode==null) // scope & key 
					{
						if (sb.Length > 0) // scope
						{
							scope = ParseScope(sb.ToString());
							sb.Length = 0;
							node = null;
							nodeType = null;
						}
						while (c != ']')
						{
							sb.Append(c);
							c = path[pos++];
						}
						sb.Append(c);
						node = CreateScopedNode(scope, sb.ToString(2, sb.Length-4), nodeType);
					}
					else
					{
						if (sb.Length > 0) // property indexer, add property first
						{
							node = new PropertyOrFieldNode(lastNode, sb.ToString(), nodeType);
							lastNode = node;
							sb.Length = 0;
							node = null;
							nodeType = null;
						}
						while (c != ']' && pos < path.Length)
						{
							sb.Append(c);
							c = path[pos++];
						}
						sb.Append(c);
						node = new IndexerNode(lastNode, sb.ToString(), nodeType);
					}
				}
				else if (c == '{')      // type 
				{
					if (sb.Length > 0) // property or scope, add property first
					{
						if (lastNode==null) // scope 
						{
							scope = ParseScope(sb.ToString());
							sb.Length = 0;
							node = null;
							nodeType = null;
						}
						else	// property
						{
							node = new PropertyOrFieldNode(lastNode, sb.ToString(), nodeType);
							lastNode = node;
							sb.Length = 0;
							node = null;
							nodeType = null;
						}
					}
					while (c != '}' && pos < path.Length)
					{
						sb.Append(c);
						c = path[pos++];
					}
					sb.Append(c);
					nodeType = ResolveType(sb.ToString());
					sb.Length=0;
				}
//				else if (c == '<')      // key 
//				{
//					if (sb.Length>0)
//					{
//						throw new SednaException("Invalid key in the path. Key should be surrounded with angle brackets.");
//					}
//
//					while (c != '>' && pos < path.Length)
//					{
//						sb.Append(c);
//						c = path[pos++];
//					}
//					sb.Append(c);
//					node = CreateScopedNode(scope, sb.ToString(), nodeType);
//				}
				if (node != null)
				{
					lastNode = node;
					sb.Length = 0;
					node = null;
					nodeType = null;
				}
			}

			return lastNode;
		}


		private  bool IsNodeSeparator(char c)
		{
			return (".([{".IndexOf(c) >= 0);
		}

		private  Scope ParseScope(string scope)
		{
			try
			{
				return (Scope)Enum.Parse(typeof(Scope), scope, true);
			}
			catch
			{
				throw new ArgumentException(String.Format("Invalid scope {0}", scope), "scope");
			}
		}

		private  Scope GetScope(string expression)
		{
			int i=expression.IndexOf('.');
			if (i<0)
			{
				throw new ArgumentException(String.Format("Invalid path {0}. Scope is not defined.", expression), "expression");
			}
			return ParseScope(expression.Substring(0, i));
        }

        #endregion 
	    
        #region Type resolution
        private  Type ResolveType(string typeName)
		{
			if (StringUtils.IsNullOrEmpty(typeName)) return null;
			if (typeName[0]=='{')
			{
				typeName=StringUtils.StripFirstAndLastCharacter(typeName);
			}
		
			Type type=null;
			if(CheckIsFullType(typeName))
			{

				type = Type.GetType(typeName);

				if(type == null)
				{
					throw new TypeLoadException(String.Format("Unable to load Type {0} specified in ScopePath.", typeName));
				}
			}

			type = ProbeWellKnownType(typeName);
			if(type != null)
			{
				return type;
			}

			type = ProbeAliases(typeName);
			if(type != null)
			{
				return type;
			}
			
			type = ProbeSednaKnownType(typeName);
			if(type != null)
			{
				return type;
			}

			throw new TypeLoadException(String.Format("Unable to load Type {0} specified in ScopePath.", typeName));
		}

		private  bool CheckIsFullType(string typeName)
		{
			return typeName.IndexOf(',') > 0;
		}

		private  Type ProbeWellKnownType(string typeName)
		{
			return Type.GetType(typeName);
		}

		private  Type ProbeAliases(string typeName)
		{
			return aliases[typeName] as Type;
		}

		private  Type ProbeSednaKnownType(string typeName)
		{
			string tmpTypeName;
			foreach(string postfix in postfixes)
			{
				tmpTypeName = typeName + postfix;
				Type tmpType = Type.GetType(tmpTypeName);
				if(tmpType != null)
				{
					return tmpType;
				}
			}

			foreach(string prefix in prefixes)
			{
				foreach(string postfix in postfixes)
				{
					tmpTypeName = prefix + typeName + postfix;
					Type tmpType = Type.GetType(tmpTypeName);
					if(tmpType != null)
					{
						return tmpType;
					}
				}
			}
			return null;
		}

		#endregion

        #region Protected Methods
	    protected virtual NavigationNode CreateScopedNode(Scope scope, string nodeExpression, Type nodeType)
	    {
            return new ScopedNode(scope, nodeExpression, nodeType);    
	    }
        #endregion
    }
}
